自定义指令:打字机效果
概述
打字机效果(Typewriter Effect)是一种常见的文本动画效果,文字逐字显示并伴有光标闪烁。本节实现 v-typewriter 自定义指令,通过 CSS 模拟光标闪烁,JS 定时器控制文字逐字显示/删除/循环。
效果展示
第一步:光标闪烁,等待开始
| ← 光标在闪烁
第二步:逐字显示文字
H| ← 逐字打印
He|
Hel|
Hello|
第三步:显示完成,暂停
第四步:逐字删除
Hell|
Hel|
He|
H|
第五步:循环回到第一步
text
指令实现
// directives/modules/typewriter.ts
import type { Directive, DirectiveBinding } from 'vue'
interface TypewriterState {
texts: string[] // 待显示的文字数组
textIndex: number // 当前文字索引
charIndex: number // 当前字符索引
isDeleting: boolean // 是否正在删除
timer: ReturnType<typeof setTimeout> | null
speed: number // 打字速度(ms/字)
deleteSpeed: number // 删除速度(ms/字)
pauseDuration: number // 显示完成后暂停时间
}
/**
* v-typewriter 指令
* 用法:<span v-typewriter="['Hello World', 'Vue 3', 'TypeScript']">初始文字</span>
*/
const typewriter: Directive<HTMLElement> = {
mounted(el: HTMLElement, binding: DirectiveBinding<string[]>) {
const texts = binding.value || [el.textContent || '']
const speed = 100
const deleteSpeed = 50
const pauseDuration = 2000
// 注入光标 CSS
injectCursorStyle(el)
const state: TypewriterState = {
texts,
textIndex: 0,
charIndex: 0,
isDeleting: false,
timer: null,
speed,
deleteSpeed,
pauseDuration
}
;(el as any)._typewriterState = state
el.textContent = ''
el.classList.add('typewriter-cursor')
startTyping(el, state)
},
beforeUnmount(el: HTMLElement) {
const state = (el as any)._typewriterState as TypewriterState | undefined
if (state?.timer) {
clearTimeout(state.timer)
}
}
}
function startTyping(el: HTMLElement, state: TypewriterState) {
const currentText = state.texts[state.textIndex]
if (!state.isDeleting) {
// 打字阶段
state.charIndex++
el.textContent = currentText.slice(0, state.charIndex)
if (state.charIndex === currentText.length) {
// 打字完成,暂停后开始删除
state.isDeleting = true
state.timer = setTimeout(() => {
startTyping(el, state)
}, state.pauseDuration)
return
}
state.timer = setTimeout(() => {
startTyping(el, state)
}, state.speed)
} else {
// 删除阶段
state.charIndex--
el.textContent = currentText.slice(0, state.charIndex)
if (state.charIndex === 0) {
// 删除完成,切换到下一段文字
state.isDeleting = false
state.textIndex = (state.textIndex + 1) % state.texts.length
state.timer = setTimeout(() => {
startTyping(el, state)
}, 500) // 短暂停顿后开始下一段
return
}
state.timer = setTimeout(() => {
startTyping(el, state)
}, state.deleteSpeed)
}
}
/**
* 注入光标闪烁样式
*/
function injectCursorStyle(el: HTMLElement) {
const styleId = 'typewriter-style'
if (document.getElementById(styleId)) return
const style = document.createElement('style')
style.id = styleId
style.textContent = `
.typewriter-cursor::after {
content: '|';
animation: typewriter-blink 0.7s infinite;
color: currentColor;
font-weight: 100;
}
@keyframes typewriter-blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
`
document.head.appendChild(style)
}
export default typewriter
typescript
使用方式
<template>
<!-- 单段文字循环 -->
<h1 v-typewriter="['Hello World']">默认文字</h1>
<!-- 多段文字循环 -->
<span v-typewriter="['Vue 3 开发', 'TypeScript 实战', '前端工程化']">
初始文字
</span>
</template>
vue
核心实现要点
| 部分 | 实现方式 | 说明 |
|---|---|---|
| 光标闪烁 | CSS ::after + @keyframes | 不占用 DOM 结构 |
| 逐字显示 | setTimeout + text.slice(0, charIndex) | 每帧截取子串 |
| 逐字删除 | charIndex-- + text.slice | 反向截取 |
| 循环切换 | (textIndex + 1) % texts.length | 循环遍历文字数组 |
| 资源清理 | beforeUnmount 清除 timer | 防止内存泄漏 |
扩展:多段文字嵌套场景
<!-- 多行文字打字效果(UL + LI) -->
<ul>
<li v-typewriter="['第一行文字']">默认</li>
<li v-typewriter="['第二行文字']">默认</li>
</ul>
vue
其他指令(课后练习)
| 指令 | 文件 | 功能 |
|---|---|---|
v-longpress | longpress.ts | 长按 2 秒触发事件 |
v-watermark | watermark.ts | Canvas 文字水印 |
v-drag | drag.ts | 元素拖拽 |
实践要点
- 打字机效果分为 CSS 光标模拟和 JS 文字控制两部分
- 光标使用
::after伪元素 + CSS 动画实现,无需额外 DOM - 文字控制使用递归
setTimeout,支持打字/删除/暂停三阶段循环 beforeUnmount必须清除定时器,避免组件销毁后仍执行- 多段文字通过数组传入,取模运算实现循环切换
↑